//
//  PowerLincUSB.m
//  Shion
//
//  Created by Chris Karr on 12/17/07.
//  Copyright 2007-2008 Audacious Software. All rights reserved.
//

#import "PowerLincUSB.h"
#import "InsteonTransmitCommand.h"
#import "DevicesModel.h"

#define LINE_SIZE 8
#define SEGMENT_SIZE 8
#define VENDOR_ID 0x10bf
#define PRODUCT_ID 0x0004

#define LOG NO

unsigned char buffer[8];

NSString * stringForData (NSData * data)
{
	NSMutableString * string = [[NSMutableString alloc] init];
	
	unsigned char * bytes = (unsigned char *) [data bytes];
		
	NSInteger i = 0;
	for (i = 0; i < [data length]; i++)
		[string appendString:[NSString stringWithFormat:@"0x%02x ", bytes[i]]];

	return string;
}

void logMessage (NSData * msg)
{
	unsigned char * bytes = (unsigned char *) [msg bytes];
	unsigned length = [msg length];

	if (length < 9)
		return;

	if (LOG)
		NSLog (@"log: %@", stringForData (msg));
		
	NSData * address = [msg subdataWithRange:NSMakeRange (0, 3)];

	// 0x0a 0xa5 0x9c 0x07 0xf7 0x6b 0x2f 0x03 0xff
	// 0x0e 0x76 0x5e 0x07 0xf7 0x6b 0x27 0x06 0xff	
	if (bytes[6] & 0x20 == 0x20)
	{
		NSMutableDictionary * dict = [NSMutableDictionary dictionary];
		[dict setValue:address forKey:@"Device"];
		[dict setValue:@"Light Level" forKey:@"Message Type"];
		[dict setValue:[NSNumber numberWithInteger:((unsigned) bytes[8])] forKey:@"Value"];

		[[NSNotificationCenter defaultCenter] postNotificationName:DEVICE_STATUS_UPDATE object:nil userInfo:dict];
	}
	else
		NSLog (@"no-log: %@", stringForData (msg));
}

unsigned responseComplete (NSData * response)
{
	unsigned length = [response length];
	unsigned char * bytes = (unsigned char *) [response bytes];
	
	if (bytes[0] == 0x02 && bytes[1] == 0x40) // Download response
	{
		if (length >= 7)
			return 7;
	}
	else if (bytes[0] == 0x02 && bytes[1] == 0x42) // Fetch data response
	{
		if (length >= 9)
		{
/*			if (19 <= length)			
			{
				if (bytes[12] & 0x8 == 0x8 && bytes[5] != 0x18)
				{
					NSLog (@"extended");
					
					NSMutableDictionary * threadDict = [[NSThread currentThread] threadDictionary];		
					PowerLincUSB * controller = [threadDict valueForKey:@"Controller"];
				
					[controller sendCommand:[[InsteonFetchExtendedCommand alloc] init]];
					
					return 19;
				}
			}
*/
			unsigned short dataLength = bytes[4];
			dataLength << 8;
			dataLength += bytes[5];
		
			if (dataLength + 23 <= length)
			{
				NSData * msg = [response subdataWithRange:NSMakeRange (6, dataLength)];
				logMessage (msg);

				return dataLength + 23;
			}
			else if (dataLength + 9 <= length)
			{
				NSData * msg = [response subdataWithRange:NSMakeRange (6, dataLength)];
				logMessage (msg);
				
				return dataLength + 9;
			}

		}
	}
	else if (bytes[0] == 0x02 && bytes[1] == 0x45) // Event Report response
	{
		if (length >= 3)
		{
			if (bytes[2] == 0x04)
			{
				// NSMutableDictionary * threadDict = [[NSThread currentThread] threadDictionary];		
				// PowerLincUSB * controller = [threadDict valueForKey:@"Controller"];
				
				// [controller sendCommand:[[InsteonFetchAckCommand alloc] init]];
				// [controller sendCommand:[[InsteonFetchExtendedCommand alloc] init]];
			}

			return 3;
		}
	}
	else if (bytes[0] == 0x02 && bytes[1] == 0x46) // Mask response
	{
		if (length >= 7)
			return 7;
	}
	else if (bytes[0] == 0x02 && bytes[1] == 0x4f) // Message Received response
	{
		if (length < 12)
		{
			if (bytes[2] == 0x05 && length >= 3)
				return 3;
			else
				return 0;
		}
		
		if (bytes[2] == 0x03)
		{
			if ((bytes[9] & 0x10) == 0x10 && length >= 26)
				return 26;
			else if ((bytes[9] & 0x10) != 0x10 && length >= 12)
				return 12;
		}
		else if (bytes[2] == 0x04)
		{
			BOOL complete = NO;
		
			if ((bytes[9] & 0x10) == 0x10 && length >= 26)
			{
				complete = YES;
				length = 26;
			}
			else if ((bytes[9] & 0x10) != 0x10 && length >= 12)
			{
				complete = YES;
				length = 12;
			}
		
			if (complete)
			{
				if (bytes[2] == 0x04)
				{
					NSData * msg = [response subdataWithRange:NSMakeRange (3, length-3)];
					logMessage (msg);
				}
			
				return length;
			}
		}
	}
	
	return 0;
}

static void callback (void *target, IOReturn result, void * o, void * p)
{
	NSMutableDictionary * threadDict = [[NSThread currentThread] threadDictionary];		
	PowerLincUSB * controller = [threadDict valueForKey:@"Controller"];
	NSString * status = [threadDict valueForKey:@"Status"];

	NSMutableData * responseData = [threadDict valueForKey:@"Response"];

	if (responseData == nil)
	{
		[threadDict setValue:[[NSMutableData alloc] init] forKey:@"Response"];
		return callback (target, result, o, p);
	}

	NSMutableData * newData = [NSMutableData data];
	
	unsigned char index = buffer[0];
	if (index >= 0x80)
		index -= 0x80;
		
	NSInteger i = 0;
	for (i = 1; i <= index; i++)
		[newData appendBytes:&buffer[i] length:1];

	[responseData appendData:newData];

	unsigned response = responseComplete (responseData);
	
	if (response > 0)
	{

		// unsigned char * bytes = (unsigned char *) [responseData bytes];

		if (LOG)
		{
			NSLog (@"R: %@", stringForData (responseData));
		}

		[responseData replaceBytesInRange:NSMakeRange (0, response) withBytes:NULL length:0];
		
		[threadDict setValue:@"Done" forKey:@"Status"];
		status = [threadDict valueForKey:@"Status"];

		[controller sendNext];
	}
	else if (buffer[0] >= 0x80)
		[controller sendNext];
}

@implementation PowerLincUSB

- (void) initHIDInterface
{
	CFMutableDictionaryRef matchingDict = NULL;
	int val;
	CFNumberRef valRef;

    matchingDict = IOServiceMatching (kIOHIDDeviceKey);
	if (matchingDict)
	{
		val = VENDOR_ID;
		valRef = CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt32Type, &val);
		CFDictionarySetValue (matchingDict, CFSTR(kIOHIDVendorIDKey), valRef);
		CFRelease (valRef);

		val = PRODUCT_ID;
		valRef = CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt32Type, &val);
		CFDictionarySetValue (matchingDict, CFSTR(kIOHIDProductIDKey), valRef);
		CFRelease (valRef);
	}

	io_service_t ioService = IOServiceGetMatchingService (kIOMasterPortDefault, matchingDict);

	IOCFPlugInInterface** iodev = NULL;
	SInt32 score;
	
	kern_return_t result = IOCreatePlugInInterfaceForService (ioService, kIOHIDDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &iodev, &score);
	if (result == KERN_SUCCESS && iodev)
	{
		result = (*iodev)->QueryInterface (iodev, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &hid);
		if (result != KERN_SUCCESS)
			NSLog (@"query = 0x%8x", result);
			
		(*iodev)->Release (iodev);
	}
	else
		return;
		
	mach_port_t port;
	CFRunLoopSourceRef eventSource;
	
	(*hid)->createAsyncPort (hid, &port);
	(*hid)->createAsyncEventSource (hid, &eventSource);

	result = (*hid)->open(hid, 0);
	if (result != KERN_SUCCESS)
		NSLog (@"open = 0x%8x", result);

	result = (*hid)->setInterruptReportHandlerCallback (hid, buffer, sizeof(buffer), (IOHIDReportCallbackFunction) callback, NULL, NULL);
	if (result != KERN_SUCCESS)
	 	NSLog (@"sirhc = 0x%8x", result);
	(*hid)->startAllQueues (hid);
	CFRunLoopAddSource (CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode);
}

- (PowerLincUSB *) init
{
	self = [super init];
	
	if (self)
	{
		hid = NULL;
		pending = [[NSMutableData alloc] init];
	}
	
	return self;
}

- (void) resend
{
	[self sendCommand:currentCommand];
}

- (void) sendNext
{
	NSMutableDictionary * threadDict = [[NSThread currentThread] threadDictionary];
	
	if ([pending length] == 0)
	{
		NSString * statusString = [threadDict valueForKey:@"Status"];
		
		if (statusString != nil && [statusString isEqual:@"Done"])
		{
			if ([currentCommand needsTransmit])
				[self sendCommand:[[InsteonTransmitCommand alloc] init]];
			else
				[self sendCommand:nil];
		}
		
		return;
	}
	
	[threadDict setValue:@"Working" forKey:@"Status"];

	unsigned int size = 3;

	NSMutableData * dataLine = [NSMutableData data];

	if (size > [pending length])
		size = [pending length];
		
	NSRange range = NSMakeRange (0, size);
		
	[dataLine appendBytes:&size length:1];
	[dataLine appendData:[pending subdataWithRange:range]];
	[pending replaceBytesInRange:range withBytes:NULL length:0];
		
	while ([dataLine length] < LINE_SIZE)
	{
		unsigned zero = 0;
		[dataLine appendBytes:&zero length:1];
	}

	size = SEGMENT_SIZE;
		
	unsigned char * bytes = (unsigned char *) [dataLine bytes];

	kern_return_t result = (*hid)->setReport (hid, kIOHIDReportTypeOutput, 0, bytes, [dataLine length], 1000, NULL, NULL, NULL);
	if (result != KERN_SUCCESS)
		NSLog (@"setReport = 0x%8x", result);
}

- (BOOL) isReady
{
	return (pending != nil && [pending length] == 0);
}

- (void) sendCommand:(InsteonCommand *) command
{
	if (hid == NULL)
		[self initHIDInterface];

	if (hid == NULL)
	{
		NSRunAlertPanel (@"Unable to Initialize", @"Unable to initialize the PowerLinc USB controller.", @"Quit", nil, nil);
		[NSApp terminate:self];
		return;
	}

	currentCommand = nil;
	
	if (command != nil)
	{
		currentCommand = command;
	
		if (pending != nil)
			[pending release];

		NSMutableDictionary * threadDict = [[NSThread currentThread] threadDictionary];
		[threadDict setValue:self forKey:@"Controller"];
		[threadDict setValue:[[NSMutableData alloc] init] forKey:@"Response"];
	
		pending = [[NSMutableData alloc] initWithData:[command commandData]];
	
		if (LOG)
		{
			NSLog (@"%@", [command className]);
			NSLog (@"S: %@", stringForData (pending));
		}
		
		[self sendNext];
	}
}

@end
